package cn.xlunzi.games.view

import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.FrameLayout
import android.widget.Scroller


class HVScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {

    private var mLastScroll: Long = 0

    private val mTempRect = Rect()
    private var mScroller: Scroller? = null

    /**
     * Flag to indicate that we are moving focus ourselves. This is so the
     * code that watches for focus changes initiated outside this ScrollView
     * knows that it does not have to do anything.
     */
    private var mScrollViewMovedFocus: Boolean = false

    /**
     * Position of the last motion event.
     */
    private var mLastMotionY: Float = 0.toFloat()
    private var mLastMotionX: Float = 0.toFloat()

    /**
     * True when the layout has changed but the traversal has not come through yet.
     * Ideally the view hierarchy would keep track of this for us.
     */
    private var mIsLayoutDirty = true

    /**
     * The child to give focus to in the event that a child has requested focus while the
     * layout is dirty. This prevents the scroll from being wrong if the child has not been
     * laid out before requesting focus.
     */
    private var mChildToScrollTo: View? = null

    /**
     * True if the user is currently dragging this ScrollView around. This is
     * not the same as 'is being flinged', which can be checked by
     * mScroller.isFinished() (flinging begins when the user lifts his finger).
     */
    private var mIsBeingDragged = false

    /**
     * Determines speed during touch scrolling
     */
    private var mVelocityTracker: VelocityTracker? = null

    /**
     * When set to true, the scroll view measure its child to make it fill the currently
     * visible area.
     */
    /**
     * Indicates whether this ScrollView's content is stretched to fill the viewport.
     *
     * @return True if the content fills the viewport, false otherwise.
     */
    /**
     * Indicates this ScrollView whether it should stretch its content height to fill
     * the viewport or not.
     *
     * @param fillViewport True to stretch the content's height to the viewport's
     * boundaries, false otherwise.
     */
    var isFillViewport: Boolean = false
        set(fillViewport) {
            if (fillViewport != isFillViewport) {
                field = fillViewport
                requestLayout()
            }
        }

    /**
     * Whether arrow scrolling is animated.
     */
    /**
     * @return Whether arrow scrolling will animate its transition.
     */
    /**
     * Set whether arrow scrolling will animate its transition.
     * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
     */
    var isSmoothScrollingEnabled = true

    private var mTouchSlop: Int = 0
    private var mMinimumVelocity: Int = 0
    private var mMaximumVelocity: Int = 0

    /**
     * ID of the active pointer. This is used to retain consistency during
     * drags/flings if multiple pointers are used.
     */
    private var mActivePointerId = INVALID_POINTER

    var isFlingEnabled = true

    /**
     * @return The maximum amount this scroll view will scroll in response to
     * an arrow event.
     */
    val maxScrollAmountV: Int
        get() = (MAX_SCROLL_FACTOR * (bottom - top)).toInt()

    val maxScrollAmountH: Int
        get() = (MAX_SCROLL_FACTOR * (right - left)).toInt()

    init {
        initScrollView()
    }

    override fun getTopFadingEdgeStrength(): Float {
        if (childCount == 0) {
            return 0.0f
        }

        val length = verticalFadingEdgeLength
        return if (scrollY < length) {
            scrollY / length.toFloat()
        } else 1.0f

    }

    override fun getLeftFadingEdgeStrength(): Float {
        if (childCount == 0) {
            return 0.0f
        }

        val length = horizontalFadingEdgeLength
        return if (scrollX < length) {
            scrollX / length.toFloat()
        } else 1.0f

    }

    override fun getRightFadingEdgeStrength(): Float {
        if (childCount == 0) {
            return 0.0f
        }

        val length = horizontalFadingEdgeLength
        val rightEdge = width - paddingRight
        val span = getChildAt(0).right - scrollX - rightEdge
        return if (span < length) {
            span / length.toFloat()
        } else 1.0f

    }

    override fun getBottomFadingEdgeStrength(): Float {
        if (childCount == 0) {
            return 0.0f
        }

        val length = verticalFadingEdgeLength
        val bottomEdge = height - paddingBottom
        val span = getChildAt(0).bottom - scrollY - bottomEdge
        return if (span < length) {
            span / length.toFloat()
        } else 1.0f

    }

    private fun initScrollView() {
        mScroller = Scroller(context)
        isFocusable = true
        descendantFocusability = FOCUS_AFTER_DESCENDANTS
        setWillNotDraw(false)
        val configuration = ViewConfiguration.get(context)
        mTouchSlop = configuration.scaledTouchSlop
        mMinimumVelocity = configuration.scaledMinimumFlingVelocity
        mMaximumVelocity = configuration.scaledMaximumFlingVelocity
    }

    override fun addView(child: View) {
        if (childCount > 0) {
            throw IllegalStateException("ScrollView can host only one direct child")
        }
        super.addView(child)
    }

    override fun addView(child: View, index: Int) {
        if (childCount > 0) {
            throw IllegalStateException("ScrollView can host only one direct child")
        }

        super.addView(child, index)
    }

    override fun addView(child: View, params: ViewGroup.LayoutParams) {
        if (childCount > 0) {
            throw IllegalStateException("ScrollView can host only one direct child")
        }

        super.addView(child, params)
    }

    override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
        if (childCount > 0) {
            throw IllegalStateException("ScrollView can host only one direct child")
        }

        super.addView(child, index, params)
    }

    /**
     * @return Returns true this ScrollView can be scrolled
     */
    private fun canScrollV(): Boolean {
        val child = getChildAt(0)
        if (child != null) {
            val childHeight = child.height
            return height < childHeight + paddingTop + paddingBottom
        }
        return false
    }

    private fun canScrollH(): Boolean {
        val child = getChildAt(0)
        if (child != null) {
            val childWidth = child.width
            return width < childWidth + paddingLeft + paddingRight
        }
        return false
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        if (!isFillViewport) {
            return
        }

        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) {
            return
        }

        if (childCount > 0) {
            val child = getChildAt(0)
            var height = measuredHeight
            var width = measuredWidth
            if (child.measuredHeight < height || child.measuredWidth < width) {
                width -= paddingLeft
                width -= paddingRight
                val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)

                height -= paddingTop
                height -= paddingBottom
                val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
            }
        }
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        // Let the focused view and/or our descendants get the key first
        return super.dispatchKeyEvent(event) || executeKeyEvent(event)
    }

    /**
     * You can call this function yourself to have the scroll view perform
     * scrolling from a key event, just as if the event had been dispatched to
     * it by the view hierarchy.
     *
     * @param event The key event to execute.
     * @return Return true if the event was handled, else false.
     */
    fun executeKeyEvent(event: KeyEvent): Boolean {
        mTempRect.setEmpty()

        var handled = false

        if (event.action == KeyEvent.ACTION_DOWN) {
            when (event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> if (canScrollH()) {
                    if (!event.isAltPressed) {
                        handled = arrowScrollH(View.FOCUS_LEFT)
                    } else {
                        handled = fullScrollH(View.FOCUS_LEFT)
                    }
                }
                KeyEvent.KEYCODE_DPAD_RIGHT -> if (canScrollH()) {
                    if (!event.isAltPressed) {
                        handled = arrowScrollH(View.FOCUS_RIGHT)
                    } else {
                        handled = fullScrollH(View.FOCUS_RIGHT)
                    }
                }
                KeyEvent.KEYCODE_DPAD_UP -> if (canScrollV()) {
                    if (!event.isAltPressed) {
                        handled = arrowScrollV(View.FOCUS_UP)
                    } else {
                        handled = fullScrollV(View.FOCUS_UP)
                    }
                }
                KeyEvent.KEYCODE_DPAD_DOWN -> if (canScrollV()) {
                    if (!event.isAltPressed) {
                        handled = arrowScrollV(View.FOCUS_DOWN)
                    } else {
                        handled = fullScrollV(View.FOCUS_DOWN)
                    }
                }
            }
        }
        return handled
    }

    private fun inChild(x: Int, y: Int): Boolean {
        if (childCount > 0) {
            val scrollX = scrollX
            val scrollY = scrollY
            val child = getChildAt(0)
            return !(y < child.top - scrollY
                    || y >= child.bottom - scrollY
                    || x < child.left - scrollX
                    || x >= child.right - scrollX)
        }
        return false
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
         * Shortcut the most recurring case: the user is in the dragging
         * state and he is moving his finger.  We want to intercept this
         * motion.
         */
        val action = ev.action
        if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
            return true
        }

        when (action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_MOVE -> {
                /*
             * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
             * whether the user has moved far enough from his original down touch.
             */

                /*
             * Locally do absolute value. mLastMotionY is set to the y value
             * of the down event.
             */
                val activePointerId = mActivePointerId
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
//                    break
                    return mIsBeingDragged
                }

                val pointerIndex = ev.findPointerIndex(activePointerId)
                val y = ev.getY(pointerIndex)
                val yDiff = Math.abs(y - mLastMotionY).toInt()
                if (yDiff > mTouchSlop) {
                    mIsBeingDragged = true
                    mLastMotionY = y
                }
                val x = ev.getX(pointerIndex)
                val xDiff = Math.abs(x - mLastMotionX).toInt()
                if (xDiff > mTouchSlop) {
                    mIsBeingDragged = true
                    mLastMotionX = x
                }
            }

            MotionEvent.ACTION_DOWN -> {
                val x = ev.x
                val y = ev.y
                if (!inChild(x.toInt(), y.toInt())) {
                    mIsBeingDragged = false
//                    break
                    return mIsBeingDragged
                }

                /*
             * Remember location of down touch.
             * ACTION_DOWN always refers to pointer index 0.
             */
                mLastMotionY = y
                mLastMotionX = x
                mActivePointerId = ev.getPointerId(0)

                /*
             * If being flinged and user touches the screen, initiate drag;
             * otherwise don't.  mScroller.isFinished should be false when
             * being flinged.
             */
                mIsBeingDragged = !mScroller!!.isFinished
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                /* Release the drag */
                mIsBeingDragged = false
                mActivePointerId = INVALID_POINTER
            }
            MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
        }

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
        return mIsBeingDragged
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {

        if (ev.action == MotionEvent.ACTION_DOWN && ev.edgeFlags != 0) {
            // Don't handle edge touches immediately -- they may actually belong to one of our
            // descendants.
            return false
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain()
        }
        mVelocityTracker!!.addMovement(ev)

        val action = ev.action

        when (action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_DOWN -> {
                val x = ev.x
                val y = ev.y
                mIsBeingDragged = inChild(x.toInt(), y.toInt())
                if (!mIsBeingDragged) {
                    return false
                }

                /*
             * If being flinged and user touches, stop the fling. isFinished
             * will be false if being flinged.
             */
                if (!mScroller!!.isFinished) {
                    mScroller!!.abortAnimation()
                }

                // Remember where the motion event started
                mLastMotionY = y
                mLastMotionX = x
                mActivePointerId = ev.getPointerId(0)
            }
            MotionEvent.ACTION_MOVE -> if (mIsBeingDragged) {
                // Scroll to follow the motion event
                val activePointerIndex = ev.findPointerIndex(mActivePointerId)
                val y = ev.getY(activePointerIndex)
                val deltaY = (mLastMotionY - y).toInt()
                mLastMotionY = y

                val x = ev.getX(activePointerIndex)
                val deltaX = (mLastMotionX - x).toInt()
                mLastMotionX = x

                scrollBy(deltaX, deltaY)
            }
            MotionEvent.ACTION_UP -> if (mIsBeingDragged) {
                if (isFlingEnabled) {
                    val velocityTracker = mVelocityTracker
                    velocityTracker!!.computeCurrentVelocity(1000, mMaximumVelocity.toFloat())
                    val initialVelocityX = velocityTracker.xVelocity.toInt()
                    val initialVelocity = velocityTracker.yVelocity.toInt()
                    //                  int initialVelocitx = (int) velocityTracker.getXVelocity(mActivePointerId);
                    //                  int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);

                    if (childCount > 0) {
                        if (Math.abs(initialVelocityX) > initialVelocityX || Math.abs(initialVelocity) > mMinimumVelocity) {
                            fling(-initialVelocityX, -initialVelocity)
                        }

                    }
                }

                mActivePointerId = INVALID_POINTER
                mIsBeingDragged = false

                if (mVelocityTracker != null) {
                    mVelocityTracker!!.recycle()
                    mVelocityTracker = null
                }
            }
            MotionEvent.ACTION_CANCEL -> if (mIsBeingDragged && childCount > 0) {
                mActivePointerId = INVALID_POINTER
                mIsBeingDragged = false
                if (mVelocityTracker != null) {
                    mVelocityTracker!!.recycle()
                    mVelocityTracker = null
                }
            }
            MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
        }
        return true
    }

    private fun onSecondaryPointerUp(ev: MotionEvent) {
        val pointerIndex = ev.action and MotionEvent.ACTION_POINTER_ID_MASK shr MotionEvent.ACTION_POINTER_ID_SHIFT
        val pointerId = ev.getPointerId(pointerIndex)
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            val newPointerIndex = if (pointerIndex == 0) 1 else 0
            mLastMotionX = ev.getX(newPointerIndex)
            mLastMotionY = ev.getY(newPointerIndex)
            mActivePointerId = ev.getPointerId(newPointerIndex)
            if (mVelocityTracker != null) {
                mVelocityTracker!!.clear()
            }
        }
    }

    /**
     *
     *
     * Finds the next focusable component that fits in the specified bounds.
     *
     *
     * @param topFocus look for a candidate is the one at the top of the bounds
     * if topFocus is true, or at the bottom of the bounds if topFocus is
     * false
     * @param top      the top offset of the bounds in which a focusable must be
     * found
     * @param bottom   the bottom offset of the bounds in which a focusable must
     * be found
     * @return the next focusable component in the bounds or null if none can
     * be found
     */
    private fun findFocusableViewInBoundsV(topFocus: Boolean, top: Int, bottom: Int): View? {

        val focusables = getFocusables(View.FOCUS_FORWARD)
        var focusCandidate: View? = null

        /*
         * A fully contained focusable is one where its top is below the bound's
         * top, and its bottom is above the bound's bottom. A partially
         * contained focusable is one where some part of it is within the
         * bounds, but it also has some part that is not within bounds.  A fully contained
         * focusable is preferred to a partially contained focusable.
         */
        var foundFullyContainedFocusable = false

        val count = focusables.size
        for (i in 0 until count) {
            val view = focusables[i]
            val viewTop = view.top
            val viewBottom = view.bottom

            if (top < viewBottom && viewTop < bottom) {
                /*
                 * the focusable is in the target area, it is a candidate for
                 * focusing
                 */

                val viewIsFullyContained = top < viewTop && viewBottom < bottom

                if (focusCandidate == null) {
                    /* No candidate, take this one */
                    focusCandidate = view
                    foundFullyContainedFocusable = viewIsFullyContained
                } else {
                    val viewIsCloserToBoundary = topFocus && viewTop < focusCandidate!!.getTop() || !topFocus && viewBottom > focusCandidate!!
                            .getBottom()

                    if (foundFullyContainedFocusable) {
                        if (viewIsFullyContained && viewIsCloserToBoundary) {
                            /*
                             * We're dealing with only fully contained views, so
                             * it has to be closer to the boundary to beat our
                             * candidate
                             */
                            focusCandidate = view
                        }
                    } else {
                        if (viewIsFullyContained) {
                            /* Any fully contained view beats a partially contained view */
                            focusCandidate = view
                            foundFullyContainedFocusable = true
                        } else if (viewIsCloserToBoundary) {
                            /*
                             * Partially contained view beats another partially
                             * contained view if it's closer
                             */
                            focusCandidate = view
                        }
                    }
                }
            }
        }

        return focusCandidate
    }

    private fun findFocusableViewInBoundsH(leftFocus: Boolean, left: Int, right: Int): View? {

        val focusables = getFocusables(View.FOCUS_FORWARD)
        var focusCandidate: View? = null

        /*
         * A fully contained focusable is one where its left is below the bound's
         * left, and its right is above the bound's right. A partially
         * contained focusable is one where some part of it is within the
         * bounds, but it also has some part that is not within bounds.  A fully contained
         * focusable is preferred to a partially contained focusable.
         */
        var foundFullyContainedFocusable = false

        val count = focusables.size
        for (i in 0 until count) {
            val view = focusables[i]
            val viewLeft = view.left
            val viewRight = view.right

            if (left < viewRight && viewLeft < right) {
                /*
                 * the focusable is in the target area, it is a candidate for
                 * focusing
                 */

                val viewIsFullyContained = left < viewLeft && viewRight < right

                if (focusCandidate == null) {
                    /* No candidate, take this one */
                    focusCandidate = view
                    foundFullyContainedFocusable = viewIsFullyContained
                } else {
                    val viewIsCloserToBoundary = leftFocus && viewLeft < focusCandidate!!.getLeft() || !leftFocus && viewRight > focusCandidate!!.getRight()

                    if (foundFullyContainedFocusable) {
                        if (viewIsFullyContained && viewIsCloserToBoundary) {
                            /*
                             * We're dealing with only fully contained views, so
                             * it has to be closer to the boundary to beat our
                             * candidate
                             */
                            focusCandidate = view
                        }
                    } else {
                        if (viewIsFullyContained) {
                            /* Any fully contained view beats a partially contained view */
                            focusCandidate = view
                            foundFullyContainedFocusable = true
                        } else if (viewIsCloserToBoundary) {
                            /*
                             * Partially contained view beats another partially
                             * contained view if it's closer
                             */
                            focusCandidate = view
                        }
                    }
                }
            }
        }

        return focusCandidate
    }

    /**
     *
     * Handles scrolling in response to a "home/end" shortcut press. This
     * method will scroll the view to the top or bottom and give the focus
     * to the topmost/bottommost component in the new visible area. If no
     * component is a good candidate for focus, this scrollview reclaims the
     * focus.
     *
     * @param direction the scroll direction: [android.view.View.FOCUS_UP]
     * to go the top of the view or
     * [android.view.View.FOCUS_DOWN] to go the bottom
     * @return true if the key event is consumed by this method, false otherwise
     */
    fun fullScrollV(direction: Int): Boolean {
        val down = direction == View.FOCUS_DOWN
        val height = height

        mTempRect.top = 0
        mTempRect.bottom = height

        if (down) {
            val count = childCount
            if (count > 0) {
                val view = getChildAt(count - 1)
                mTempRect.bottom = view.bottom
                mTempRect.top = mTempRect.bottom - height
            }
        }

        return scrollAndFocusV(direction, mTempRect.top, mTempRect.bottom)
    }

    fun fullScrollH(direction: Int): Boolean {
        val right = direction == View.FOCUS_RIGHT
        val width = width

        mTempRect.left = 0
        mTempRect.right = width

        if (right) {
            val count = childCount
            if (count > 0) {
                val view = getChildAt(0)
                mTempRect.right = view.right
                mTempRect.left = mTempRect.right - width
            }
        }

        return scrollAndFocusH(direction, mTempRect.left, mTempRect.right)
    }

    /**
     *
     * Scrolls the view to make the area defined by `top` and
     * `bottom` visible. This method attempts to give the focus
     * to a component visible in this area. If no component can be focused in
     * the new visible area, the focus is reclaimed by this scrollview.
     *
     * @param direction the scroll direction: [android.view.View.FOCUS_UP]
     * to go upward
     * [android.view.View.FOCUS_DOWN] to downward
     * @param top       the top offset of the new area to be made visible
     * @param bottom    the bottom offset of the new area to be made visible
     * @return true if the key event is consumed by this method, false otherwise
     */
    private fun scrollAndFocusV(direction: Int, top: Int, bottom: Int): Boolean {
        var handled = true

        val height = height
        val containerTop = scrollY
        val containerBottom = containerTop + height
        val up = direction == View.FOCUS_UP

        var newFocused = findFocusableViewInBoundsV(up, top, bottom)
        if (newFocused == null) {
            newFocused = this
        }

        if (top >= containerTop && bottom <= containerBottom) {
            handled = false
        } else {
            val delta = if (up) top - containerTop else bottom - containerBottom
            doScrollY(delta)
        }

        if (newFocused !== findFocus() && newFocused!!.requestFocus(direction)) {
            mScrollViewMovedFocus = true
            mScrollViewMovedFocus = false
        }

        return handled
    }

    private fun scrollAndFocusH(direction: Int, left: Int, right: Int): Boolean {
        var handled = true

        val width = width
        val containerLeft = scrollX
        val containerRight = containerLeft + width
        val goLeft = direction == View.FOCUS_LEFT

        var newFocused = findFocusableViewInBoundsH(goLeft, left, right)
        if (newFocused == null) {
            newFocused = this
        }

        if (left >= containerLeft && right <= containerRight) {
            handled = false
        } else {
            val delta = if (goLeft) left - containerLeft else right - containerRight
            doScrollX(delta)
        }

        if (newFocused !== findFocus() && newFocused!!.requestFocus(direction)) {
            mScrollViewMovedFocus = true
            mScrollViewMovedFocus = false
        }

        return handled
    }

    /**
     * Handle scrolling in response to an up or down arrow click.
     *
     * @param direction The direction corresponding to the arrow key that was
     * pressed
     * @return True if we consumed the event, false otherwise
     */
    fun arrowScrollV(direction: Int): Boolean {

        var currentFocused: View? = findFocus()
        if (currentFocused === this) currentFocused = null

        val nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction)

        val maxJump = maxScrollAmountV

        if (nextFocused != null && isWithinDeltaOfScreenV(nextFocused, maxJump, height)) {
            nextFocused.getDrawingRect(mTempRect)
            offsetDescendantRectToMyCoords(nextFocused, mTempRect)
            val scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect)
            doScrollY(scrollDelta)
            nextFocused.requestFocus(direction)
        } else {
            // no new focus
            var scrollDelta = maxJump

            if (direction == View.FOCUS_UP && scrollY < scrollDelta) {
                scrollDelta = scrollY
            } else if (direction == View.FOCUS_DOWN) {
                if (childCount > 0) {
                    val daBottom = getChildAt(0).bottom

                    val screenBottom = scrollY + height

                    if (daBottom - screenBottom < maxJump) {
                        scrollDelta = daBottom - screenBottom
                    }
                }
            }
            if (scrollDelta == 0) {
                return false
            }
            doScrollY(if (direction == View.FOCUS_DOWN) scrollDelta else -scrollDelta)
        }

        if (currentFocused != null && currentFocused!!.isFocused()
                && isOffScreenV(currentFocused)) {
            // previously focused item still has focus and is off screen, give
            // it up (take it back to ourselves)
            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
            // sure to
            // get it)
            val descendantFocusability = descendantFocusability  // save
            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
            requestFocus()
            setDescendantFocusability(descendantFocusability)  // restore
        }
        return true
    }

    fun arrowScrollH(direction: Int): Boolean {

        var currentFocused: View? = findFocus()
        if (currentFocused === this) currentFocused = null

        val nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction)

        val maxJump = maxScrollAmountH

        if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) {
            nextFocused.getDrawingRect(mTempRect)
            offsetDescendantRectToMyCoords(nextFocused, mTempRect)
            val scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect)
            doScrollX(scrollDelta)
            nextFocused.requestFocus(direction)
        } else {
            // no new focus
            var scrollDelta = maxJump

            if (direction == View.FOCUS_LEFT && scrollX < scrollDelta) {
                scrollDelta = scrollX
            } else if (direction == View.FOCUS_RIGHT && childCount > 0) {

                val daRight = getChildAt(0).right

                val screenRight = scrollX + width

                if (daRight - screenRight < maxJump) {
                    scrollDelta = daRight - screenRight
                }
            }
            if (scrollDelta == 0) {
                return false
            }
            doScrollX(if (direction == View.FOCUS_RIGHT) scrollDelta else -scrollDelta)
        }

        if (currentFocused != null && currentFocused!!.isFocused()
                && isOffScreenH(currentFocused)) {
            // previously focused item still has focus and is off screen, give
            // it up (take it back to ourselves)
            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
            // sure to
            // get it)
            val descendantFocusability = descendantFocusability  // save
            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
            requestFocus()
            setDescendantFocusability(descendantFocusability)  // restore
        }
        return true
    }

    /**
     * @return whether the descendant of this scroll view is scrolled off
     * screen.
     */
    private fun isOffScreenV(descendant: View): Boolean {
        return !isWithinDeltaOfScreenV(descendant, 0, height)
    }

    private fun isOffScreenH(descendant: View): Boolean {
        return !isWithinDeltaOfScreenH(descendant, 0)
    }

    /**
     * @return whether the descendant of this scroll view is within delta
     * pixels of being on the screen.
     */
    private fun isWithinDeltaOfScreenV(descendant: View, delta: Int, height: Int): Boolean {
        descendant.getDrawingRect(mTempRect)
        offsetDescendantRectToMyCoords(descendant, mTempRect)

        return mTempRect.bottom + delta >= scrollY && mTempRect.top - delta <= scrollY + height
    }

    private fun isWithinDeltaOfScreenH(descendant: View, delta: Int): Boolean {
        descendant.getDrawingRect(mTempRect)
        offsetDescendantRectToMyCoords(descendant, mTempRect)

        return mTempRect.right + delta >= scrollX && mTempRect.left - delta <= scrollX + width
    }

    /**
     * Smooth scroll by a Y delta
     *
     * @param delta the number of pixels to scroll by on the Y axis
     */
    private fun doScrollY(delta: Int) {
        if (delta != 0) {
            if (isSmoothScrollingEnabled) {
                smoothScrollBy(0, delta)
            } else {
                scrollBy(0, delta)
            }
        }
    }

    private fun doScrollX(delta: Int) {
        if (delta != 0) {
            if (isSmoothScrollingEnabled) {
                smoothScrollBy(delta, 0)
            } else {
                scrollBy(delta, 0)
            }
        }
    }

    /**
     * Like [View.scrollBy], but scroll smoothly instead of immediately.
     *
     * @param dx the number of pixels to scroll by on the X axis
     * @param dy the number of pixels to scroll by on the Y axis
     */
    fun smoothScrollBy(dx: Int, dy: Int) {
        var dx = dx
        var dy = dy
        if (childCount == 0) {
            // Nothing to do.
            return
        }
        val duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll
        if (duration > ANIMATED_SCROLL_GAP) {
            val height = height - paddingBottom - paddingTop
            val bottom = getChildAt(0).height
            val maxY = Math.max(0, bottom - height)
            val scrollY = scrollY
            dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY

            val width = width - paddingRight - paddingLeft
            val right = getChildAt(0).width
            val maxX = Math.max(0, right - width)
            val scrollX = scrollX
            dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX

            mScroller!!.startScroll(scrollX, scrollY, dx, dy)
            invalidate()
        } else {
            if (!mScroller!!.isFinished) {
                mScroller!!.abortAnimation()
            }
            scrollBy(dx, dy)
        }
        mLastScroll = AnimationUtils.currentAnimationTimeMillis()
    }

    /**
     * Like [.scrollTo], but scroll smoothly instead of immediately.
     *
     * @param x the position where to scroll on the X axis
     * @param y the position where to scroll on the Y axis
     */
    fun smoothScrollTo(x: Int, y: Int) {
        smoothScrollBy(x - scrollX, y - scrollY)
    }

    /**
     *
     * The scroll range of a scroll view is the overall height of all of its
     * children.
     */
    override fun computeVerticalScrollRange(): Int {
        val count = childCount
        val contentHeight = height - paddingBottom - paddingTop
        return if (count == 0) {
            contentHeight
        } else getChildAt(0).bottom

    }

    override fun computeHorizontalScrollRange(): Int {
        val count = childCount
        val contentWidth = width - paddingLeft - paddingRight
        return if (count == 0) {
            contentWidth
        } else getChildAt(0).right

    }

    override fun computeVerticalScrollOffset(): Int {
        return Math.max(0, super.computeVerticalScrollOffset())
    }

    override fun computeHorizontalScrollOffset(): Int {
        return Math.max(0, super.computeHorizontalScrollOffset())
    }

    override protected fun measureChild(child: View, parentWidthMeasureSpec: Int, parentHeightMeasureSpec: Int) {
        val childWidthMeasureSpec: Int
        val childHeightMeasureSpec: Int

        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    }

    override fun measureChildWithMargins(child: View, parentWidthMeasureSpec: Int, widthUsed: Int,
                                         parentHeightMeasureSpec: Int, heightUsed: Int) {
        val lp = child.getLayoutParams() as MarginLayoutParams

        val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED)
        val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED)

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    }

    override fun computeScroll() {
        if (mScroller!!.computeScrollOffset()) {
            // This is called at drawing time by ViewGroup.  We don't want to
            // re-show the scrollbars at this point, which scrollTo will do,
            // so we replicate most of scrollTo here.
            //
            //         It's a little odd to call onScrollChanged from inside the drawing.
            //
            //         It is, except when you remember that computeScroll() is used to
            //         animate scrolling. So unless we want to defer the onScrollChanged()
            //         until the end of the animated scrolling, we don't really have a
            //         choice here.
            //
            //         I agree.  The alternative, which I think would be worse, is to post
            //         something and tell the subclasses later.  This is bad because there
            //         will be a window where mScrollX/Y is different from what the app
            //         thinks it is.
            //
            var x = mScroller!!.currX
            var y = mScroller!!.currY

            if (childCount > 0) {
                val child = getChildAt(0)
                x = clamp(x, width - paddingRight - paddingLeft, child.width)
                y = clamp(y, height - paddingBottom - paddingTop, child.height)
                super.scrollTo(x, y)
            }
            awakenScrollBars()

            // Keep on drawing until the animation has finished.
            postInvalidate()
        }
    }

    /**
     * Scrolls the view to the given child.
     *
     * @param child the View to scroll to
     */
    private fun scrollToChild(child: View) {
        child.getDrawingRect(mTempRect)

        /* Offset from child's local coordinates to ScrollView coordinates */
        offsetDescendantRectToMyCoords(child, mTempRect)

        val scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect)
        val scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect)

        if (scrollDeltaH != 0 || scrollDeltaV != 0) {
            scrollBy(scrollDeltaH, scrollDeltaV)
        }
    }

    /**
     * If rect is off screen, scroll just enough to get it (or at least the
     * first screen size chunk of it) on screen.
     *
     * @param rect      The rectangle.
     * @param immediate True to scroll immediately without animation
     * @return true if scrolling was performed
     */
    private fun scrollToChildRect(rect: Rect, immediate: Boolean): Boolean {
        val deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect)
        val deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect)
        val scroll = deltaH != 0 || deltaV != 0
        if (scroll) {
            if (immediate) {
                scrollBy(deltaH, deltaV)
            } else {
                smoothScrollBy(deltaH, deltaV)
            }
        }
        return scroll
    }

    /**
     * Compute the amount to scroll in the Y direction in order to get
     * a rectangle completely on the screen (or, if taller than the screen,
     * at least the first screen size chunk of it).
     *
     * @param rect The rect.
     * @return The scroll delta.
     */
    protected fun computeScrollDeltaToGetChildRectOnScreenV(rect: Rect): Int {
        if (childCount == 0) return 0

        val height = height
        var screenTop = scrollY
        var screenBottom = screenTop + height

        val fadingEdge = verticalFadingEdgeLength

        // leave room for top fading edge as long as rect isn't at very top
        if (rect.top > 0) {
            screenTop += fadingEdge
        }

        // leave room for bottom fading edge as long as rect isn't at very bottom
        if (rect.bottom < getChildAt(0).height) {
            screenBottom -= fadingEdge
        }

        var scrollYDelta = 0

        if (rect.bottom > screenBottom && rect.top > screenTop) {
            // need to move down to get it in view: move down just enough so
            // that the entire rectangle is in view (or at least the first
            // screen size chunk).

            if (rect.height() > height) {
                // just enough to get screen size chunk on
                scrollYDelta += rect.top - screenTop
            } else {
                // get entire rect at bottom of screen
                scrollYDelta += rect.bottom - screenBottom
            }

            // make sure we aren't scrolling beyond the end of our content
            val bottom = getChildAt(0).bottom
            val distanceToBottom = bottom - screenBottom
            scrollYDelta = Math.min(scrollYDelta, distanceToBottom)

        } else if (rect.top < screenTop && rect.bottom < screenBottom) {
            // need to move up to get it in view: move up just enough so that
            // entire rectangle is in view (or at least the first screen
            // size chunk of it).

            if (rect.height() > height) {
                // screen size chunk
                scrollYDelta -= screenBottom - rect.bottom
            } else {
                // entire rect at top
                scrollYDelta -= screenTop - rect.top
            }

            // make sure we aren't scrolling any further than the top our content
            scrollYDelta = Math.max(scrollYDelta, -scrollY)
        }
        return scrollYDelta
    }

    protected fun computeScrollDeltaToGetChildRectOnScreenH(rect: Rect): Int {
        if (childCount == 0) return 0

        val width = width
        var screenLeft = scrollX
        var screenRight = screenLeft + width

        val fadingEdge = horizontalFadingEdgeLength

        // leave room for left fading edge as long as rect isn't at very left
        if (rect.left > 0) {
            screenLeft += fadingEdge
        }

        // leave room for right fading edge as long as rect isn't at very right
        if (rect.right < getChildAt(0).width) {
            screenRight -= fadingEdge
        }

        var scrollXDelta = 0

        if (rect.right > screenRight && rect.left > screenLeft) {
            // need to move right to get it in view: move right just enough so
            // that the entire rectangle is in view (or at least the first
            // screen size chunk).

            if (rect.width() > width) {
                // just enough to get screen size chunk on
                scrollXDelta += rect.left - screenLeft
            } else {
                // get entire rect at right of screen
                scrollXDelta += rect.right - screenRight
            }

            // make sure we aren't scrolling beyond the end of our content
            val right = getChildAt(0).right
            val distanceToRight = right - screenRight
            scrollXDelta = Math.min(scrollXDelta, distanceToRight)

        } else if (rect.left < screenLeft && rect.right < screenRight) {
            // need to move right to get it in view: move right just enough so that
            // entire rectangle is in view (or at least the first screen
            // size chunk of it).

            if (rect.width() > width) {
                // screen size chunk
                scrollXDelta -= screenRight - rect.right
            } else {
                // entire rect at left
                scrollXDelta -= screenLeft - rect.left
            }

            // make sure we aren't scrolling any further than the left our content
            scrollXDelta = Math.max(scrollXDelta, -scrollX)
        }
        return scrollXDelta
    }

    override fun requestChildFocus(child: View, focused: View) {
        if (!mScrollViewMovedFocus) {
            if (!mIsLayoutDirty) {
                scrollToChild(focused)
            } else {
                // The child may not be laid out yet, we can't compute the scroll yet
                mChildToScrollTo = focused
            }
        }
        super.requestChildFocus(child, focused)
    }


    /**
     * When looking for focus in children of a scroll view, need to be a little
     * more careful not to give focus to something that is scrolled off screen.
     *
     * This is more expensive than the default [android.view.ViewGroup]
     * implementation, otherwise this behavior might have been made the default.
     */
    override fun onRequestFocusInDescendants(direction: Int,
                                             previouslyFocusedRect: Rect?): Boolean {

        // convert from forward / backward notation to up / down / left / right
        // (ugh).
        // TODO: FUCK
        //        if (direction == View.FOCUS_FORWARD) {
        //            direction = View.FOCUS_RIGHT;
        //        } else if (direction == View.FOCUS_BACKWARD) {
        //            direction = View.FOCUS_LEFT;
        //        }

        val nextFocus = (if (previouslyFocusedRect == null)
            FocusFinder.getInstance().findNextFocus(this, null, direction)
        else
            FocusFinder.getInstance().findNextFocusFromRect(this,
                    previouslyFocusedRect, direction)) ?: return false

//        if (isOffScreenH(nextFocus)) {
        //            return false;
        //        }

        return nextFocus.requestFocus(direction, previouslyFocusedRect)
    }

    override fun requestChildRectangleOnScreen(child: View, rectangle: Rect,
                                               immediate: Boolean): Boolean {
        // offset into coordinate space of this scroll view
        rectangle.offset(child.getLeft() - child.getScrollX(),
                child.getTop() - child.getScrollY())

        return scrollToChildRect(rectangle, immediate)
    }

    override fun requestLayout() {
        mIsLayoutDirty = true
        super.requestLayout()
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        mIsLayoutDirty = false
        // Give a child focus if it needs it
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo!!, this)) {
            scrollToChild(mChildToScrollTo!!)
        }
        mChildToScrollTo = null

        // Calling this with the present values causes it to re-clam them
        scrollTo(scrollX, scrollY)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)

        val currentFocused = findFocus()
        if (null == currentFocused || this === currentFocused)
            return

        // If the currently-focused view was visible on the screen when the
        // screen was at the old height, then scroll the screen to make that
        // view visible with the new screen height.
        if (isWithinDeltaOfScreenV(currentFocused, 0, oldh)) {
            currentFocused.getDrawingRect(mTempRect)
            offsetDescendantRectToMyCoords(currentFocused, mTempRect)
            val scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect)
            doScrollY(scrollDelta)
        }

        val maxJump = right - left
        if (isWithinDeltaOfScreenH(currentFocused, maxJump)) {
            currentFocused.getDrawingRect(mTempRect)
            offsetDescendantRectToMyCoords(currentFocused, mTempRect)
            val scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect)
            doScrollX(scrollDelta)
        }
    }

    /**
     * Return true if child is an descendant of parent, (or equal to the parent).
     */
    private fun isViewDescendantOf(child: View, parent: View): Boolean {
        if (child === parent) {
            return true
        }

        val theParent = child.getParent()
        return theParent is ViewGroup && isViewDescendantOf(theParent as View, parent)
    }

    /**
     * Fling the scroll view
     *
     * @param velocityY The initial velocity in the Y direction. Positive
     * numbers mean that the finger/cursor is moving down the screen,
     * which means we want to scroll towards the top.
     */
    fun fling(velocityX: Int, velocityY: Int) {
        if (childCount > 0) {
            val width = width - paddingRight - paddingLeft
            val right = getChildAt(0).width

            val height = height - paddingBottom - paddingTop
            val bottom = getChildAt(0).height

            mScroller!!.fling(scrollX, scrollY, velocityX, velocityY,
                    0, Math.max(0, right - width),
                    0, Math.max(0, bottom - height))

            //            final boolean movingDown = velocityX > 0 || velocityY > 0;
            //
            //            View newFocused =
            //                    findFocusableViewInMyBoundsV(movingDown, mScroller.getFinalY(), findFocus());
            //            if (newFocused == null) {
            //                newFocused = this;
            //            }
            //
            //            if (newFocused != findFocus()
            //                    && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
            //                mScrollViewMovedFocus = true;
            //                mScrollViewMovedFocus = false;
            //            }

            invalidate()
        }
    }

    /**
     * {@inheritDoc}
     *
     *
     * This version also clamps the scrolling to the bounds of our child.
     */
    override fun scrollTo(x: Int, y: Int) {
        var x = x
        var y = y
        // we rely on the fact the View.scrollBy calls scrollTo.
        if (childCount > 0) {
            val child = getChildAt(0)
            x = clamp(x, width - paddingRight - paddingLeft, child.width)
            y = clamp(y, height - paddingBottom - paddingTop, child.height)
            if (x != scrollX || y != scrollY) {
                super.scrollTo(x, y)
            }
        }
    }

    private fun clamp(n: Int, my: Int, child: Int): Int {
        if (my >= child || n < 0) {
            /* my >= child is this case:
             *                    |--------------- me ---------------|
             *     |------ child ------|
             * or
             *     |--------------- me ---------------|
             *            |------ child ------|
             * or
             *     |--------------- me ---------------|
             *                                  |------ child ------|
             *
             * n < 0 is this case:
             *     |------ me ------|
             *                    |-------- child --------|
             *     |-- mScrollX --|
             */
            return 0
        }
        return if (my + n > child) {
            /* this case:
         *                    |------ me ------|
         *     |------ child ------|
         *     |-- mScrollX --|
         */
            child - my
        } else n
    }

    companion object {
        internal val ANIMATED_SCROLL_GAP = 250

        internal val MAX_SCROLL_FACTOR = 0.5f

        /**
         * Sentinel value for no current active pointer.
         * Used by [.mActivePointerId].
         */
        private val INVALID_POINTER = -1
    }
}